using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;

using AGG;
using AGG.Transform;
using AGG.Image;
using AGG.VertexSource;
using AGG.UI;

namespace Fabinator
{
    public class VertexSourceGCodeView : IVertexSource
    {
        int currentVertexIndex;
        int currentLayer;
        GCode gCodeModel;
        double currentZ;
        List<int> firstVertexIndexInValidLayer = new List<int>();

        public VertexSourceGCodeView(GCode layer)
        {
            gCodeModel = layer;

            switch (gCodeModel.IndexOfChangeInZ.Count)
            {
                case 0:
                    firstVertexIndexInValidLayer.Add(0);
                    break;

                case 1:
                    throw new NotImplementedException("even one change in z should be valid");
                    break;

                default:
                    {
                        for (int i = 0; i < gCodeModel.IndexOfChangeInZ.Count - 1; i++)
                        {
                            double testZ = gCodeModel.GCodeVertices[gCodeModel.IndexOfChangeInZ[i]].position.z;
                            int numVerteciesWithSameZ = 0;
                            for (int j = gCodeModel.IndexOfChangeInZ[i]; j < gCodeModel.IndexOfChangeInZ[i + 1]; j++)
                            {
                                if (gCodeModel.GCodeVertices[j].position.z == testZ)
                                {
                                    numVerteciesWithSameZ++;
                                }
                                else
                                {
                                    break;
                                }
                            }

                            //bool nextVertexDoesExtruding = gCodeModel.GCodeVertices[gCodeModel.IndexOfChangeInZ[i]+1].ExtrudeOn;
                            if (numVerteciesWithSameZ > 3)
                            {
                                firstVertexIndexInValidLayer.Add(gCodeModel.IndexOfChangeInZ[i]);
                            }
                        }
                    }
                    break;
            }
        }

        public int NumLayers
        {
            get { return firstVertexIndexInValidLayer.Count; }
        }

        public int GetNumSegmentsForLayer(int layerIndex)
        {
            return 1;
        }

        public int[] GetColorForSegmentsForLayer(int layerIndex)
        {
            int[] temp = new int[] { 1 };
            return temp;
        }

        public void rewind(int layerIndex)
        {
            currentLayer = layerIndex;
            currentVertexIndex = firstVertexIndexInValidLayer[layerIndex];
            currentZ = gCodeModel.GCodeVertices[currentVertexIndex].Z;
        }

        public AGG.Path.FlagsAndCommand vertex(out double x, out double y)
        {
            AGG.Path.FlagsAndCommand command;
            x = gCodeModel.GCodeVertices[currentVertexIndex].Position.x;
            y = gCodeModel.GCodeVertices[currentVertexIndex].Position.y;
            if (currentVertexIndex < gCodeModel.GCodeVertices.Count - 1
                && currentZ == gCodeModel.GCodeVertices[currentVertexIndex].Z)
            {
                if (currentVertexIndex == 0)
                {
                    command = AGG.Path.FlagsAndCommand.CommandMoveTo;
                }
                else
                {
                    command = AGG.Path.FlagsAndCommand.CommandLineTo;
                }
                currentVertexIndex++;
            }
            else
            {
                command = AGG.Path.FlagsAndCommand.CommandStop;
            }

            return command;
        }

        public rect_d GetBounds()
        {
            rect_d bounds = new rect_d(double.MaxValue, double.MaxValue, double.MinValue, double.MinValue);
            rewind(0);

            double x;
            double y;
            while(vertex(out x, out y) != AGG.Path.FlagsAndCommand.CommandStop)
            {
                bounds.Left = Math.Min(x, bounds.Left);
                bounds.Right = Math.Max(x, bounds.Right);
                bounds.Bottom = Math.Min(x, bounds.Bottom);
                bounds.Top = Math.Max(x, bounds.Top);
            }

            return bounds;
        }
    }

    public class GCodeVertex
    {
        public GCodeVertex()
        {
        }
    }

    public class FabricationMachineState
    {
        public Vector3D position = new Vector3D();
        public double feedRate = 0;
        public bool extrudeOn = false;
        public bool movmentIsAbsolute;

        public FabricationMachineState()
        {
        }

        public FabricationMachineState(FabricationMachineState copy)
        {
            position = copy.position;
            feedRate = copy.feedRate;
            extrudeOn = copy.extrudeOn;
            movmentIsAbsolute = copy.movmentIsAbsolute;
        }

        public Vector3D Position
        {
            get { return position; }
        }

        public bool ExtrudeOn
        {
            get { return extrudeOn; }
            set { extrudeOn = value; }
        }

        public double X
        {
            get { return position.x; }
            set
            {
                if (movmentIsAbsolute)
                {
                    position.x = value;
                }
                else
                {
                    position.x += value;
                }
            }
        }

        public double Y
        {
            get { return position.y; }
            set
            {
                if (movmentIsAbsolute)
                {
                    position.y = value;
                }
                else
                {
                    position.y += value;
                }
            }
        }

        public double Z
        {
            get { return position.z; }
            set
            {
                if (movmentIsAbsolute)
                {
                    position.z = value;
                }
                else
                {
                    position.z += value;
                }
            }
        }

        public double FeedRate
        {
            get { return feedRate; }
            set { feedRate = value; }
        }
    }

    public class GCode
    {
        // a good ref for this is http://www.linuxcnc.org/docs/html/gcode_main.html#sub:G89:-Boring,-Dwell
        List<int> indexOfChangeInZ = new List<int>();
        List<FabricationMachineState> gCodeVertices = new List<FabricationMachineState>();
        String gcodeFileContents;
        Vector2D center;
        double parsingLastZ;

        public Vector2D Center
        {
            get {return center; }
        }

        public GCode()
        {
        }

        public List<int> IndexOfChangeInZ
        {
            get { return indexOfChangeInZ; }
        }

        public List<FabricationMachineState> GCodeVertices
        {
            get { return gCodeVertices; }
        }

        public int NumChangesInZ
        {
            get { return indexOfChangeInZ.Count; }
        }

        public void LoadGCode(string fileName)
        {
            StreamReader streamReader = new StreamReader(fileName);

            LoadGCode(streamReader.BaseStream);
        }

        public void LoadGCode(Stream streamReader)
        {
            gcodeFileContents = "";
            center.Zero();

            byte[] buffer = new byte[streamReader.Length];
            streamReader.Read(buffer, 0, buffer.Length);
            System.Text.ASCIIEncoding ASCIIEncode = new System.Text.ASCIIEncoding();
            gcodeFileContents = ASCIIEncode.GetString(buffer);
            streamReader.Close();

            if (gcodeFileContents.Contains("Table.MoveTo("))
            {
                ParseZenString(gcodeFileContents);
            }
            else
            {
                ParseGCodeString(gcodeFileContents);
            }
        }

        void ParseMLine(string lineString, FabricationMachineState machineState)
        {
            string[] splitOnSpace = lineString.Split(' ');
            switch (splitOnSpace[0].Substring(1).Trim())
            {
                case "01":
                    // show a message?
                    break;

                case "6":
                    // wait for tool to heat up (wait for condition?)
                    break;

                case "101":
                    // extrude on, forward
                    machineState.ExtrudeOn = true;
                    break;

                case "18":
                    // turn off stepers
                    break;
				
                case "102":
                    // extrude on reverse
                    machineState.ExtrudeOn = false;
                    break;

                case "103":
                    // extrude off
                    machineState.ExtrudeOn = false;
                    break;

                case "104":
                    // set extruder tempreature
                    break;

                case "105":
                    // M105 Custom code for temperature reading. (Not used)
                    break;

                case "106":
                    // turn fan on
                    break;

                case "107":
                    // turn fan off
                    break;

                case "108":
                    // set extruder speed
                    break;

                case "109":
                    // set heated platform temperature
                    break;
				
				case "132":
					// recall stored home offsets for axis xyzab
					break;
				
                default:
                    throw new NotImplementedException();
            }
        }

        void ParseGLine(string lineString, FabricationMachineState machineState)
        {
            string[] splitOnSpace = lineString.Split(' ');
            switch (splitOnSpace[0].Substring(1).Trim())
            {
                case "0":
                    // goto coordinate (have to read it)
                    break;

                case "04":
                    // wait a given number of miliseconds
                    break;

                case "1":
                    // get the x y z to move to
                    for (int argIndex = 1; argIndex < splitOnSpace.Length; argIndex++)
                    {
                        double value;
                        if (splitOnSpace[argIndex].Length > 0 && double.TryParse(splitOnSpace[argIndex].Substring(1), NumberStyles.Number, null, out value))
                        {
                            switch (splitOnSpace[argIndex][0])
                            {
                                case 'X':
                                    machineState.X = value;
                                    break;

                                case 'Y':
                                    machineState.Y = value;
                                    break;

                                case 'Z':
                                    machineState.Z = value;
                                    break;

                                case 'F':
                                    machineState.FeedRate = value;
                                    break;
                            }
                        }
                    }

                    gCodeVertices.Add(new FabricationMachineState(machineState));
                    if (machineState.Z != parsingLastZ)
                    {
                        indexOfChangeInZ.Add(gCodeVertices.Count - 1);
                    }
                    parsingLastZ = machineState.Position.z;
                    break;

                case "21":
                    // set to metric
                    break;

                case "28":
                    // G28 	Return to home position (machine zero, aka machine reference point)
                    break;

                case "90": // G90 is Absolute Distance Mode
                    machineState.movmentIsAbsolute = true;
                    break;

                case "91": // G91 is Incremental Distance Mode
                    machineState.movmentIsAbsolute = false;
                    break;

                case "92":
                    // set current head position values (used to reset origin)
                    break;

				case "161":
					// home x,y axis minimum
					break;
				
				case "162":
					// home z axis maximum
					break;

                default:
                    throw new NotImplementedException();
            }
        }

        public void ParseZenString(String zenString)
        {
            // these are the only two line currently described in Zen Table
            //Table.MoveTo(24400,7253)
            //Table.MoveToExitSpeed(24529,7253,50)

            FabricationMachineState machineState = new FabricationMachineState();
            // a zen table is always moving sand so it is always on (not a perfect analogy could make something better in the future, like a zen table machine.
            machineState.ExtrudeOn = true;
            machineState.movmentIsAbsolute = true;

            string[] splitOnNL = zenString.Split('\n');
            foreach (string lineString in splitOnNL)
            {
                if (lineString.Length > 0)
                {
                    if (lineString.StartsWith("Table.MoveTo("))
                    {
                        machineState.ExtrudeOn = false;
                    }
                    else if (lineString.StartsWith("Table.MoveToExitSpeed("))
                    {
                        machineState.ExtrudeOn = true;
                    }
                    int openParensIndex = lineString.IndexOf('(');
                    int closeParensIndex = lineString.IndexOf(')');
                    string stuffBetweenParens = lineString.Substring(openParensIndex + 1, (closeParensIndex - openParensIndex) - 1);
                    string[] splitOnComma = stuffBetweenParens.Split(',');
                    double value;
                    if (double.TryParse(splitOnComma[0], NumberStyles.Number, null, out value))
                    {
                        machineState.X = value;
                    }
                    if (double.TryParse(splitOnComma[1], NumberStyles.Number, null, out value))
                    {
                        machineState.Y = value;
                    }
                }
                gCodeVertices.Add(new FabricationMachineState(machineState));
            }
        }

        public void ParseGCodeString(String gCodeString)
        {
            FabricationMachineState machineState = new FabricationMachineState();
            string[] splitOnNL = gCodeString.Split('\n');
            foreach (string lineString in splitOnNL)
            {
                if (lineString.Length > 0)
                {
                    switch (lineString[0])
                    {
                        case 'M':
                            ParseMLine(lineString, machineState);
                            break;

                        case 'G':
                            ParseGLine(lineString, machineState);
                            break;

                        default:
                            break;
                    }
                }
            }
        }
    }
}
